// Copyright 2021 - 2025 Crunchy Data Solutions, Inc.
//
// SPDX-License-Identifier: Apache-2.0

package pgbackrest

import (
	"context"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"gotest.tools/v3/assert"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"

	"github.com/crunchydata/postgres-operator/internal/initialize"
	"github.com/crunchydata/postgres-operator/internal/naming"
	"github.com/crunchydata/postgres-operator/internal/postgres"
	"github.com/crunchydata/postgres-operator/internal/testing/cmp"
	"github.com/crunchydata/postgres-operator/internal/testing/require"
	"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
)

func TestCreatePGBackRestConfigMapIntent(t *testing.T) {
	cluster := v1beta1.PostgresCluster{}
	cluster.Namespace = "ns1"
	cluster.Name = "hippo-dance"

	cluster.Spec.Port = initialize.Int32(2345)
	cluster.Spec.PostgresVersion = 12

	domain := naming.KubernetesClusterDomain(context.Background())

	t.Run("NoRepos", func(t *testing.T) {
		// We always create the config for the pgbackrest instance and server
		cluster := cluster.DeepCopy()
		cluster.Spec.Backups.PGBackRest.Repos = nil
		cluster.UID = "piano"

		configmap, err := CreatePGBackRestConfigMapIntent(context.Background(), cluster,
			"", "number", "pod-service-name", "test-ns", "",
			[]string{"some-instance"})

		assert.NilError(t, err)
		assert.Equal(t, configmap.Data["config-hash"], "number")
		assert.Equal(t, configmap.Data["pgbackrest-server.conf"], strings.Trim(`
# Generated by postgres-operator. DO NOT EDIT.
# Your changes will not be saved.

[global]
tls-server-address = 0.0.0.0
tls-server-auth = pgbackrest@piano=*
tls-server-ca-file = /etc/pgbackrest/conf.d/~postgres-operator/tls-ca.crt
tls-server-cert-file = /etc/pgbackrest/server/server-tls.crt
tls-server-key-file = /etc/pgbackrest/server/server-tls.key

[global:server]
log-level-console = detail
log-level-file = off
log-level-stderr = error
log-timestamp = n
		`, "\t\n")+"\n")

		assert.Equal(t, configmap.Data["pgbackrest_instance.conf"], strings.Trim(`
# Generated by postgres-operator. DO NOT EDIT.
# Your changes will not be saved.

[global]
archive-async = y
log-path = /pgdata/pgbackrest/log
spool-path = /pgdata/pgbackrest-spool

[db]
pg1-path = /pgdata/pg12
pg1-port = 2345
pg1-socket-path = /tmp/postgres
		`, "\t\n")+"\n")

		assert.Equal(t, configmap.Data["pgbackrest_repo.conf"], "")
		assert.Equal(t, configmap.Data["pgbackrest_cloud.conf"], "")
	})

	t.Run("CloudRepoPresentNoVolumeRepo", func(t *testing.T) {
		cluster := cluster.DeepCopy()
		cluster.UID = "ukulele"
		cluster.Spec.Backups.PGBackRest.Global = map[string]string{
			"repo1-test": "something",
		}
		cluster.Spec.Backups.PGBackRest.Repos = []v1beta1.PGBackRestRepo{
			{
				Name: "repo1",
				GCS:  &v1beta1.RepoGCS{Bucket: "g-bucket"},
			},
		}

		configmap, err := CreatePGBackRestConfigMapIntent(context.Background(), cluster,
			"", "anumber", "pod-service-name", "test-ns", "",
			[]string{"some-instance"})
		assert.NilError(t, err)

		configmapWithCloudLogging, err := CreatePGBackRestConfigMapIntent(context.Background(), cluster,
			"", "anumber", "pod-service-name", "test-ns", "/a/log/path",
			[]string{"some-instance"})

		assert.NilError(t, err)
		assert.DeepEqual(t, configmap.Annotations, map[string]string{})
		assert.DeepEqual(t, configmapWithCloudLogging.Annotations, map[string]string{})

		assert.DeepEqual(t, configmap.Labels, map[string]string{
			"postgres-operator.crunchydata.com/cluster":           "hippo-dance",
			"postgres-operator.crunchydata.com/pgbackrest":        "",
			"postgres-operator.crunchydata.com/pgbackrest-config": "",
		})
		assert.DeepEqual(t, configmapWithCloudLogging.Labels, map[string]string{
			"postgres-operator.crunchydata.com/cluster":           "hippo-dance",
			"postgres-operator.crunchydata.com/pgbackrest":        "",
			"postgres-operator.crunchydata.com/pgbackrest-config": "",
		})

		assert.Equal(t, configmap.Data["config-hash"], "anumber")
		assert.Equal(t, configmapWithCloudLogging.Data["config-hash"], "anumber")

		serverConfigExpectation := strings.Trim(`
# Generated by postgres-operator. DO NOT EDIT.
# Your changes will not be saved.

[global]
tls-server-address = 0.0.0.0
tls-server-auth = pgbackrest@ukulele=*
tls-server-ca-file = /etc/pgbackrest/conf.d/~postgres-operator/tls-ca.crt
tls-server-cert-file = /etc/pgbackrest/server/server-tls.crt
tls-server-key-file = /etc/pgbackrest/server/server-tls.key

[global:server]
log-level-console = detail
log-level-file = off
log-level-stderr = error
log-timestamp = n
		`, "\t\n")
		assert.Equal(t, configmap.Data["pgbackrest-server.conf"], serverConfigExpectation+"\n")
		assert.Equal(t, configmapWithCloudLogging.Data["pgbackrest-server.conf"], serverConfigExpectation+"\n")

		instanceConfigExpectation := strings.Trim(`
# Generated by postgres-operator. DO NOT EDIT.
# Your changes will not be saved.

[global]
archive-async = y
log-path = /pgdata/pgbackrest/log
repo1-gcs-bucket = g-bucket
repo1-path = /pgbackrest/repo1
repo1-test = something
repo1-type = gcs
spool-path = /pgdata/pgbackrest-spool

[db]
pg1-path = /pgdata/pg12
pg1-port = 2345
pg1-socket-path = /tmp/postgres
		`, "\t\n")
		assert.Equal(t, configmap.Data["pgbackrest_instance.conf"], instanceConfigExpectation+"\n")
		assert.Equal(t, configmapWithCloudLogging.Data["pgbackrest_instance.conf"], instanceConfigExpectation+"\n")

		assert.Equal(t, configmap.Data["pgbackrest_cloud.conf"], strings.Trim(`
# Generated by postgres-operator. DO NOT EDIT.
# Your changes will not be saved.

[global]
log-level-file = off
repo1-gcs-bucket = g-bucket
repo1-path = /pgbackrest/repo1
repo1-test = something
repo1-type = gcs

[db]
pg1-host = some-instance-0.pod-service-name.test-ns.svc.`+domain+`
pg1-host-ca-file = /etc/pgbackrest/conf.d/~postgres-operator/tls-ca.crt
pg1-host-cert-file = /etc/pgbackrest/conf.d/~postgres-operator/client-tls.crt
pg1-host-key-file = /etc/pgbackrest/conf.d/~postgres-operator/client-tls.key
pg1-host-type = tls
pg1-path = /pgdata/pg12
pg1-port = 2345
pg1-socket-path = /tmp/postgres
		`, "\t\n")+"\n")

		assert.Equal(t, configmapWithCloudLogging.Data["pgbackrest_cloud.conf"], strings.Trim(`
# Generated by postgres-operator. DO NOT EDIT.
# Your changes will not be saved.

[global]
log-path = /a/log/path
repo1-gcs-bucket = g-bucket
repo1-path = /pgbackrest/repo1
repo1-test = something
repo1-type = gcs

[db]
pg1-host = some-instance-0.pod-service-name.test-ns.svc.`+domain+`
pg1-host-ca-file = /etc/pgbackrest/conf.d/~postgres-operator/tls-ca.crt
pg1-host-cert-file = /etc/pgbackrest/conf.d/~postgres-operator/client-tls.crt
pg1-host-key-file = /etc/pgbackrest/conf.d/~postgres-operator/client-tls.key
pg1-host-type = tls
pg1-path = /pgdata/pg12
pg1-port = 2345
pg1-socket-path = /tmp/postgres
		`, "\t\n")+"\n")

		assert.Equal(t, configmap.Data["pgbackrest_repo.conf"], "")
		assert.Equal(t, configmapWithCloudLogging.Data["pgbackrest_repo.conf"], "")
	})

	t.Run("VolumeRepoPresentNoCloudRepo", func(t *testing.T) {
		cluster := cluster.DeepCopy()
		cluster.UID = "guitar"
		cluster.Spec.Backups.PGBackRest.Repos = []v1beta1.PGBackRestRepo{
			{
				Name:   "repo1",
				Volume: &v1beta1.RepoPVC{},
			},
		}

		configmap, err := CreatePGBackRestConfigMapIntent(context.Background(), cluster,
			"repo-hostname", "anumber", "pod-service-name", "test-ns", "",
			[]string{"some-instance"})

		assert.NilError(t, err)
		assert.DeepEqual(t, configmap.Annotations, map[string]string{})
		assert.DeepEqual(t, configmap.Labels, map[string]string{
			"postgres-operator.crunchydata.com/cluster":           "hippo-dance",
			"postgres-operator.crunchydata.com/pgbackrest":        "",
			"postgres-operator.crunchydata.com/pgbackrest-config": "",
		})

		assert.Equal(t, configmap.Data["config-hash"], "anumber")
		assert.Equal(t, configmap.Data["pgbackrest-server.conf"], strings.Trim(`
# Generated by postgres-operator. DO NOT EDIT.
# Your changes will not be saved.

[global]
tls-server-address = 0.0.0.0
tls-server-auth = pgbackrest@guitar=*
tls-server-ca-file = /etc/pgbackrest/conf.d/~postgres-operator/tls-ca.crt
tls-server-cert-file = /etc/pgbackrest/server/server-tls.crt
tls-server-key-file = /etc/pgbackrest/server/server-tls.key

[global:server]
log-level-console = detail
log-level-file = off
log-level-stderr = error
log-timestamp = n
		`, "\t\n")+"\n")

		assert.Equal(t, configmap.Data["pgbackrest_instance.conf"], strings.Trim(`
# Generated by postgres-operator. DO NOT EDIT.
# Your changes will not be saved.

[global]
archive-async = y
log-path = /pgdata/pgbackrest/log
repo1-host = repo-hostname-0.pod-service-name.test-ns.svc.`+domain+`
repo1-host-ca-file = /etc/pgbackrest/conf.d/~postgres-operator/tls-ca.crt
repo1-host-cert-file = /etc/pgbackrest/conf.d/~postgres-operator/client-tls.crt
repo1-host-key-file = /etc/pgbackrest/conf.d/~postgres-operator/client-tls.key
repo1-host-type = tls
repo1-host-user = postgres
repo1-path = /pgbackrest/repo1
spool-path = /pgdata/pgbackrest-spool

[db]
pg1-path = /pgdata/pg12
pg1-port = 2345
pg1-socket-path = /tmp/postgres
		`, "\t\n")+"\n")

		assert.Equal(t, configmap.Data["pgbackrest_repo.conf"], strings.Trim(`
# Generated by postgres-operator. DO NOT EDIT.
# Your changes will not be saved.

[global]
log-path = /pgbackrest/repo1/log
repo1-path = /pgbackrest/repo1

[db]
pg1-host = some-instance-0.pod-service-name.test-ns.svc.`+domain+`
pg1-host-ca-file = /etc/pgbackrest/conf.d/~postgres-operator/tls-ca.crt
pg1-host-cert-file = /etc/pgbackrest/conf.d/~postgres-operator/client-tls.crt
pg1-host-key-file = /etc/pgbackrest/conf.d/~postgres-operator/client-tls.key
pg1-host-type = tls
pg1-path = /pgdata/pg12
pg1-port = 2345
pg1-socket-path = /tmp/postgres
		`, "\t\n")+"\n")

		assert.Equal(t, configmap.Data["pgbackrest_cloud.conf"], "")
	})

	t.Run("DedicatedRepoHostAndCloudRepos", func(t *testing.T) {
		cluster := cluster.DeepCopy()
		cluster.UID = "bass"
		cluster.Spec.Backups.PGBackRest.Global = map[string]string{
			"repo3-test": "something",
		}
		cluster.Spec.Backups.PGBackRest.Repos = []v1beta1.PGBackRestRepo{
			{
				Name:   "repo1",
				Volume: &v1beta1.RepoPVC{},
			},
			{
				Name:  "repo2",
				Azure: &v1beta1.RepoAzure{Container: "a-container"},
			},
			{
				Name: "repo3",
				GCS:  &v1beta1.RepoGCS{Bucket: "g-bucket"},
			},
			{
				Name: "repo4",
				S3: &v1beta1.RepoS3{
					Bucket: "s-bucket", Endpoint: "endpoint-s", Region: "earth",
				},
			},
		}

		configmap, err := CreatePGBackRestConfigMapIntent(context.Background(), cluster,
			"repo-hostname", "abcde12345", "pod-service-name", "test-ns", "",
			[]string{"some-instance"})

		assert.NilError(t, err)
		assert.DeepEqual(t, configmap.Annotations, map[string]string{})
		assert.DeepEqual(t, configmap.Labels, map[string]string{
			"postgres-operator.crunchydata.com/cluster":           "hippo-dance",
			"postgres-operator.crunchydata.com/pgbackrest":        "",
			"postgres-operator.crunchydata.com/pgbackrest-config": "",
		})

		assert.Equal(t, configmap.Data["config-hash"], "abcde12345")

		assert.Equal(t, configmap.Data["pgbackrest-server.conf"], strings.Trim(`
# Generated by postgres-operator. DO NOT EDIT.
# Your changes will not be saved.

[global]
tls-server-address = 0.0.0.0
tls-server-auth = pgbackrest@bass=*
tls-server-ca-file = /etc/pgbackrest/conf.d/~postgres-operator/tls-ca.crt
tls-server-cert-file = /etc/pgbackrest/server/server-tls.crt
tls-server-key-file = /etc/pgbackrest/server/server-tls.key

[global:server]
log-level-console = detail
log-level-file = off
log-level-stderr = error
log-timestamp = n
		`, "\t\n")+"\n")

		assert.Equal(t, configmap.Data["pgbackrest_repo.conf"], strings.Trim(`
# Generated by postgres-operator. DO NOT EDIT.
# Your changes will not be saved.

[global]
log-path = /pgbackrest/repo1/log
repo1-path = /pgbackrest/repo1
repo2-azure-container = a-container
repo2-path = /pgbackrest/repo2
repo2-type = azure
repo3-gcs-bucket = g-bucket
repo3-path = /pgbackrest/repo3
repo3-test = something
repo3-type = gcs
repo4-path = /pgbackrest/repo4
repo4-s3-bucket = s-bucket
repo4-s3-endpoint = endpoint-s
repo4-s3-region = earth
repo4-type = s3

[db]
pg1-host = some-instance-0.pod-service-name.test-ns.svc.`+domain+`
pg1-host-ca-file = /etc/pgbackrest/conf.d/~postgres-operator/tls-ca.crt
pg1-host-cert-file = /etc/pgbackrest/conf.d/~postgres-operator/client-tls.crt
pg1-host-key-file = /etc/pgbackrest/conf.d/~postgres-operator/client-tls.key
pg1-host-type = tls
pg1-path = /pgdata/pg12
pg1-port = 2345
pg1-socket-path = /tmp/postgres
		`, "\t\n")+"\n")

		assert.Equal(t, configmap.Data["pgbackrest_instance.conf"], strings.Trim(`
# Generated by postgres-operator. DO NOT EDIT.
# Your changes will not be saved.

[global]
archive-async = y
log-path = /pgdata/pgbackrest/log
repo1-host = repo-hostname-0.pod-service-name.test-ns.svc.`+domain+`
repo1-host-ca-file = /etc/pgbackrest/conf.d/~postgres-operator/tls-ca.crt
repo1-host-cert-file = /etc/pgbackrest/conf.d/~postgres-operator/client-tls.crt
repo1-host-key-file = /etc/pgbackrest/conf.d/~postgres-operator/client-tls.key
repo1-host-type = tls
repo1-host-user = postgres
repo1-path = /pgbackrest/repo1
repo2-azure-container = a-container
repo2-path = /pgbackrest/repo2
repo2-type = azure
repo3-gcs-bucket = g-bucket
repo3-path = /pgbackrest/repo3
repo3-test = something
repo3-type = gcs
repo4-path = /pgbackrest/repo4
repo4-s3-bucket = s-bucket
repo4-s3-endpoint = endpoint-s
repo4-s3-region = earth
repo4-type = s3
spool-path = /pgdata/pgbackrest-spool

[db]
pg1-path = /pgdata/pg12
pg1-port = 2345
pg1-socket-path = /tmp/postgres
		`, "\t\n")+"\n")

		assert.Equal(t, configmap.Data["pgbackrest_cloud.conf"], strings.Trim(`
# Generated by postgres-operator. DO NOT EDIT.
# Your changes will not be saved.

[global]
log-level-file = off
repo2-azure-container = a-container
repo2-path = /pgbackrest/repo2
repo2-type = azure
repo3-gcs-bucket = g-bucket
repo3-path = /pgbackrest/repo3
repo3-test = something
repo3-type = gcs
repo4-path = /pgbackrest/repo4
repo4-s3-bucket = s-bucket
repo4-s3-endpoint = endpoint-s
repo4-s3-region = earth
repo4-type = s3

[db]
pg1-host = some-instance-0.pod-service-name.test-ns.svc.`+domain+`
pg1-host-ca-file = /etc/pgbackrest/conf.d/~postgres-operator/tls-ca.crt
pg1-host-cert-file = /etc/pgbackrest/conf.d/~postgres-operator/client-tls.crt
pg1-host-key-file = /etc/pgbackrest/conf.d/~postgres-operator/client-tls.key
pg1-host-type = tls
pg1-path = /pgdata/pg12
pg1-port = 2345
pg1-socket-path = /tmp/postgres
		`, "\t\n")+"\n")
	})

	t.Run("LoggingToAdditionalVolume", func(t *testing.T) {
		cluster := cluster.DeepCopy()
		cluster.UID = "guitar"
		cluster.Spec.Backups.PGBackRest.Repos = []v1beta1.PGBackRestRepo{
			{
				Name:   "repo1",
				Volume: &v1beta1.RepoPVC{},
			},
		}
		cluster.Spec.Backups.PGBackRest.RepoHost = &v1beta1.PGBackRestRepoHost{
			Log: &v1beta1.LoggingConfiguration{
				Path: "/volumes/test",
			},
			Volumes: &v1beta1.PGBackRestVolumesSpec{
				Additional: []v1beta1.AdditionalVolume{
					{
						ClaimName: "some-pvc",
						Name:      "test",
					},
				},
			},
		}

		configmap, err := CreatePGBackRestConfigMapIntent(context.Background(), cluster,
			"repo-hostname", "anumber", "pod-service-name", "test-ns", "",
			[]string{"some-instance"})

		assert.NilError(t, err)
		assert.DeepEqual(t, configmap.Annotations, map[string]string{})
		assert.DeepEqual(t, configmap.Labels, map[string]string{
			"postgres-operator.crunchydata.com/cluster":           "hippo-dance",
			"postgres-operator.crunchydata.com/pgbackrest":        "",
			"postgres-operator.crunchydata.com/pgbackrest-config": "",
		})

		assert.Equal(t, configmap.Data["config-hash"], "anumber")
		assert.Equal(t, configmap.Data["pgbackrest-server.conf"], strings.Trim(`
# Generated by postgres-operator. DO NOT EDIT.
# Your changes will not be saved.

[global]
tls-server-address = 0.0.0.0
tls-server-auth = pgbackrest@guitar=*
tls-server-ca-file = /etc/pgbackrest/conf.d/~postgres-operator/tls-ca.crt
tls-server-cert-file = /etc/pgbackrest/server/server-tls.crt
tls-server-key-file = /etc/pgbackrest/server/server-tls.key

[global:server]
log-level-console = detail
log-level-file = off
log-level-stderr = error
log-timestamp = n
		`, "\t\n")+"\n")

		assert.Equal(t, configmap.Data["pgbackrest_instance.conf"], strings.Trim(`
# Generated by postgres-operator. DO NOT EDIT.
# Your changes will not be saved.

[global]
archive-async = y
log-path = /pgdata/pgbackrest/log
repo1-host = repo-hostname-0.pod-service-name.test-ns.svc.`+domain+`
repo1-host-ca-file = /etc/pgbackrest/conf.d/~postgres-operator/tls-ca.crt
repo1-host-cert-file = /etc/pgbackrest/conf.d/~postgres-operator/client-tls.crt
repo1-host-key-file = /etc/pgbackrest/conf.d/~postgres-operator/client-tls.key
repo1-host-type = tls
repo1-host-user = postgres
repo1-path = /pgbackrest/repo1
spool-path = /pgdata/pgbackrest-spool

[db]
pg1-path = /pgdata/pg12
pg1-port = 2345
pg1-socket-path = /tmp/postgres
		`, "\t\n")+"\n")

		assert.Equal(t, configmap.Data["pgbackrest_repo.conf"], strings.Trim(`
# Generated by postgres-operator. DO NOT EDIT.
# Your changes will not be saved.

[global]
log-path = /volumes/test
repo1-path = /pgbackrest/repo1

[db]
pg1-host = some-instance-0.pod-service-name.test-ns.svc.`+domain+`
pg1-host-ca-file = /etc/pgbackrest/conf.d/~postgres-operator/tls-ca.crt
pg1-host-cert-file = /etc/pgbackrest/conf.d/~postgres-operator/client-tls.crt
pg1-host-key-file = /etc/pgbackrest/conf.d/~postgres-operator/client-tls.key
pg1-host-type = tls
pg1-path = /pgdata/pg12
pg1-port = 2345
pg1-socket-path = /tmp/postgres
		`, "\t\n")+"\n")

		assert.Equal(t, configmap.Data["pgbackrest_cloud.conf"], "")
	})

	t.Run("CustomMetadata", func(t *testing.T) {
		cluster := cluster.DeepCopy()
		cluster.Spec.Metadata = &v1beta1.Metadata{
			Annotations: map[string]string{
				"ak1": "cluster-av1",
				"ak2": "cluster-av2",
			},
			Labels: map[string]string{
				"lk1": "cluster-lv1",
				"lk2": "cluster-lv2",

				"postgres-operator.crunchydata.com/cluster": "cluster-ignored",
			},
		}
		cluster.Spec.Backups.PGBackRest.Metadata = &v1beta1.Metadata{
			Annotations: map[string]string{
				"ak2": "backups-av2",
				"ak3": "backups-av3",
			},
			Labels: map[string]string{
				"lk2": "backups-lv2",
				"lk3": "backups-lv3",

				"postgres-operator.crunchydata.com/cluster": "backups-ignored",
			},
		}

		configmap, err := CreatePGBackRestConfigMapIntent(context.Background(), cluster,
			"any", "any", "any", "any", "any", nil)

		assert.NilError(t, err)
		assert.DeepEqual(t, configmap.Annotations, map[string]string{
			"ak1": "cluster-av1",
			"ak2": "backups-av2",
			"ak3": "backups-av3",
		})
		assert.DeepEqual(t, configmap.Labels, map[string]string{
			"lk1": "cluster-lv1",
			"lk2": "backups-lv2",
			"lk3": "backups-lv3",

			"postgres-operator.crunchydata.com/cluster":           "hippo-dance",
			"postgres-operator.crunchydata.com/pgbackrest":        "",
			"postgres-operator.crunchydata.com/pgbackrest-config": "",
		})
	})
}

func TestMakePGBackrestLogDir(t *testing.T) {
	podTemplate := &corev1.PodTemplateSpec{Spec: corev1.PodSpec{
		InitContainers: []corev1.Container{
			{Name: "test"},
		},
		Containers: []corev1.Container{
			{Name: "pgbackrest",
				Resources: corev1.ResourceRequirements{
					Limits: corev1.ResourceList{
						corev1.ResourceCPU: resource.MustParse("23m"),
					},
				},
			},
		}}}

	cluster := &v1beta1.PostgresCluster{
		Spec: v1beta1.PostgresClusterSpec{
			ImagePullPolicy: corev1.PullAlways,
			Backups: v1beta1.Backups{
				PGBackRest: v1beta1.PGBackRestArchive{
					Image: "test-image",
					Repos: []v1beta1.PGBackRestRepo{
						{Name: "repo1"},
						{Name: "repo2",
							Volume: &v1beta1.RepoPVC{},
						},
					},
				},
			},
		},
	}

	beforeAddInit := podTemplate.Spec.InitContainers

	MakePGBackrestLogDir(podTemplate, cluster)

	assert.Equal(t, len(beforeAddInit)+1, len(podTemplate.Spec.InitContainers))

	var foundInitContainer bool
	// verify init container command, image & name
	for _, c := range podTemplate.Spec.InitContainers {
		if c.Name == naming.ContainerPGBackRestLogDirInit {
			// ignore "bash -c", should skip repo with no volume
			assert.Equal(t, `mkdir -p '/pgbackrest/repo2/log' && { chmod 0775 '/pgbackrest/repo2/log' || :; }`, c.Command[2])
			assert.Equal(t, c.Image, "test-image")
			assert.Equal(t, c.ImagePullPolicy, corev1.PullAlways)
			assert.Assert(t, !cmp.DeepEqual(c.SecurityContext,
				&corev1.SecurityContext{})().Success())
			assert.Equal(t, c.Resources.Limits.Cpu().String(), "23m")
			foundInitContainer = true
			break
		}
	}
	// verify init container is present
	assert.Assert(t, foundInitContainer)
}

func TestReloadCommand(t *testing.T) {
	shellcheck := require.ShellCheck(t)

	repo1Size := resource.MustParse("1Gi")
	repo2Size := resource.MustParse("2Gi")
	repo3Size := resource.MustParse("3Gi")
	repo4Size := resource.MustParse("4Gi")

	// Create a command with all four repos having auto-grow enabled.
	// Check that the generated script has the correct values for each repo
	// when calling manageAutogrowAnnotation.
	//
	// Note that the actual values for trigger and max-grow are not important
	// here, just that they are correctly passed through to the script.
	command := reloadCommand("some-name", []v1beta1.PGBackRestRepo{{
		Name: "repo1",
		Volume: &v1beta1.RepoPVC{
			VolumeClaimSpec: v1beta1.VolumeClaimSpecWithAutoGrow{
				AutoGrow: &v1beta1.AutoGrowSpec{
					Trigger: initialize.Int32(10),
					MaxGrow: &repo1Size,
				},
			},
		},
	}, {
		Name: "repo2",
		Volume: &v1beta1.RepoPVC{
			VolumeClaimSpec: v1beta1.VolumeClaimSpecWithAutoGrow{
				AutoGrow: &v1beta1.AutoGrowSpec{
					Trigger: initialize.Int32(20),
					MaxGrow: &repo2Size,
				},
			},
		},
	}, {
		Name: "repo3",
		Volume: &v1beta1.RepoPVC{
			VolumeClaimSpec: v1beta1.VolumeClaimSpecWithAutoGrow{
				AutoGrow: &v1beta1.AutoGrowSpec{
					Trigger: initialize.Int32(30),
					MaxGrow: &repo3Size,
				},
			},
		},
	}, {
		Name: "repo4",
		Volume: &v1beta1.RepoPVC{
			VolumeClaimSpec: v1beta1.VolumeClaimSpecWithAutoGrow{
				AutoGrow: &v1beta1.AutoGrowSpec{
					Trigger: initialize.Int32(40),
					MaxGrow: &repo4Size,
				},
			},
		},
	}})

	// Expect a bash command with an inline script.
	assert.DeepEqual(t, command[:3], []string{"bash", "-ceu", "--"})
	assert.Assert(t, len(command) > 3)

	// Write out that inline script.
	dir := t.TempDir()
	file := filepath.Join(dir, "script.bash")
	assert.NilError(t, os.WriteFile(file, []byte(command[3]), 0o600))

	// Expect shellcheck to be happy.
	cmd := exec.CommandContext(t.Context(), shellcheck, "--enable=all", file)
	output, err := cmd.CombinedOutput()
	assert.NilError(t, err, "%q\n%s", cmd.Args, output)

	assert.Assert(t, cmp.Contains(command[3], "manageAutogrowAnnotation \"repo1\" \"10\" \"1024\""))
	assert.Assert(t, cmp.Contains(command[3], "manageAutogrowAnnotation \"repo2\" \"20\" \"2048\""))
	assert.Assert(t, cmp.Contains(command[3], "manageAutogrowAnnotation \"repo3\" \"30\" \"3072\""))
	assert.Assert(t, cmp.Contains(command[3], "manageAutogrowAnnotation \"repo4\" \"40\" \"4096\""))

}

func TestReloadCommandPrettyYAML(t *testing.T) {
	assert.Assert(t, cmp.MarshalContains(reloadCommand("any", nil), "\n- |"),
		"expected literal block scalar")
}

func TestRestoreCommand(t *testing.T) {
	shellcheck := require.ShellCheck(t)

	command := RestoreCommand(19, "/pgdata/pg13", postgres.NewParameterSet(), "--repo=1")

	assert.DeepEqual(t, command[:3], []string{"bash", "-ceu", "--"})
	assert.Assert(t, len(command) > 3)

	assert.Assert(t, cmp.Contains(command[3], "/usr/pgsql-19/bin"),
		"expected path to PostgreSQL binaries")

	dir := t.TempDir()
	file := filepath.Join(dir, "script.bash")
	assert.NilError(t, os.WriteFile(file, []byte(command[3]), 0o600))

	cmd := exec.CommandContext(t.Context(), shellcheck, "--enable=all", file)
	output, err := cmd.CombinedOutput()
	assert.NilError(t, err, "%q\n%s", cmd.Args, output)
}

func TestRestoreCommandPrettyYAML(t *testing.T) {
	assert.Assert(t,
		cmp.MarshalContains(
			RestoreCommand(9, "/dir", postgres.NewParameterSet(), "--options"),
			"\n- |",
		),
		"expected literal block scalar")
}

func TestDedicatedSnapshotVolumeRestoreCommand(t *testing.T) {
	shellcheck := require.ShellCheck(t)

	pgdata := "/pgdata/pg13"
	opts := []string{
		"--stanza=" + DefaultStanzaName, "--pg1-path=" + pgdata,
		"--repo=1"}
	command := DedicatedSnapshotVolumeRestoreCommand(pgdata, strings.Join(opts, " "))

	assert.DeepEqual(t, command[:3], []string{"bash", "-ceu", "--"})
	assert.Assert(t, len(command) > 3)

	dir := t.TempDir()
	file := filepath.Join(dir, "script.bash")
	assert.NilError(t, os.WriteFile(file, []byte(command[3]), 0o600))

	cmd := exec.CommandContext(t.Context(), shellcheck, "--enable=all", file)
	output, err := cmd.CombinedOutput()
	assert.NilError(t, err, "%q\n%s", cmd.Args, output)
}

func TestDedicatedSnapshotVolumeRestoreCommandPrettyYAML(t *testing.T) {
	assert.Assert(t,
		cmp.MarshalContains(
			DedicatedSnapshotVolumeRestoreCommand("/dir", "--options"),
			"\n- |",
		),
		"expected literal block scalar")
}

func TestServerConfig(t *testing.T) {
	cluster := &v1beta1.PostgresCluster{}
	cluster.UID = "shoe"

	assert.Equal(t, serverConfig(cluster).String(), `
[global]
tls-server-address = 0.0.0.0
tls-server-auth = pgbackrest@shoe=*
tls-server-ca-file = /etc/pgbackrest/conf.d/~postgres-operator/tls-ca.crt
tls-server-cert-file = /etc/pgbackrest/server/server-tls.crt
tls-server-key-file = /etc/pgbackrest/server/server-tls.key

[global:server]
log-level-console = detail
log-level-file = off
log-level-stderr = error
log-timestamp = n
`)
}

func TestServerConfigIPv6(t *testing.T) {
	cluster := &v1beta1.PostgresCluster{}
	cluster.UID = "shoe"
	cluster.Annotations = map[string]string{
		naming.PGBackRestIPVersion: "IPv6",
	}

	assert.Equal(t, serverConfig(cluster).String(), `
[global]
tls-server-address = ::
tls-server-auth = pgbackrest@shoe=*
tls-server-ca-file = /etc/pgbackrest/conf.d/~postgres-operator/tls-ca.crt
tls-server-cert-file = /etc/pgbackrest/server/server-tls.crt
tls-server-key-file = /etc/pgbackrest/server/server-tls.key

[global:server]
log-level-console = detail
log-level-file = off
log-level-stderr = error
log-timestamp = n
`)
}

func TestGenerateRepoHostLogPath(t *testing.T) {
	cluster := v1beta1.PostgresCluster{}
	cluster.Namespace = "ns1"
	cluster.Name = "hippo-dance"

	cluster.Spec.Port = initialize.Int32(2345)
	cluster.Spec.PostgresVersion = 12

	cluster.Spec.Backups = v1beta1.Backups{
		PGBackRest: v1beta1.PGBackRestArchive{},
	}

	t.Run("NoReposNoRepoHost", func(t *testing.T) {
		cluster := cluster.DeepCopy()
		assert.Equal(t, generateRepoHostLogPath(cluster), "")
	})

	t.Run("NoVolumeRepo", func(t *testing.T) {
		cluster := cluster.DeepCopy()
		cluster.Spec.Backups.PGBackRest.Repos = []v1beta1.PGBackRestRepo{
			{
				Name: "repo1",
				GCS:  &v1beta1.RepoGCS{},
			},
		}
		assert.Equal(t, generateRepoHostLogPath(cluster), "")
	})

	t.Run("OneVolumeRepo", func(t *testing.T) {
		cluster := cluster.DeepCopy()
		cluster.Spec.Backups.PGBackRest.Repos = []v1beta1.PGBackRestRepo{
			{
				Name:   "repo1",
				Volume: &v1beta1.RepoPVC{},
			},
		}
		assert.Equal(t, generateRepoHostLogPath(cluster), "/pgbackrest/repo1/log")
	})

	t.Run("TwoVolumeRepos", func(t *testing.T) {
		cluster := cluster.DeepCopy()
		cluster.Spec.Backups.PGBackRest.Repos = []v1beta1.PGBackRestRepo{
			{
				Name:   "repo1",
				Volume: &v1beta1.RepoPVC{},
			},
			{
				Name:   "repo2",
				Volume: &v1beta1.RepoPVC{},
			},
		}
		assert.Equal(t, generateRepoHostLogPath(cluster), "/pgbackrest/repo1/log")
	})

	t.Run("VolumeRepoNotFirst", func(t *testing.T) {
		cluster := cluster.DeepCopy()
		cluster.Spec.Backups.PGBackRest.Repos = []v1beta1.PGBackRestRepo{
			{
				Name: "repo1",
				GCS:  &v1beta1.RepoGCS{},
			},
			{
				Name:   "repo2",
				Volume: &v1beta1.RepoPVC{},
			},
		}
		assert.Equal(t, generateRepoHostLogPath(cluster), "/pgbackrest/repo2/log")
	})

	t.Run("LogPathSpecified", func(t *testing.T) {
		cluster := cluster.DeepCopy()
		cluster.Spec.Backups.PGBackRest.Repos = []v1beta1.PGBackRestRepo{
			{
				Name:   "repo1",
				Volume: &v1beta1.RepoPVC{},
			},
		}
		cluster.Spec.Backups.PGBackRest.RepoHost = &v1beta1.PGBackRestRepoHost{
			Log: &v1beta1.LoggingConfiguration{
				Path: "/some/directory",
			},
		}
		assert.Equal(t, generateRepoHostLogPath(cluster), "/some/directory")
	})
}
